﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;


namespace PresentItNow
{
    /// <summary>
    /// <para>This class provides the possibility to run methods that look synchronously in an asynchronous fashion.</para>       
    /// <para>For this a method has to have a return type of IEnumerator&lt;SwitchExecutionContext&gt;</para>
    /// <para>Whenever a "yield return SwitchExecutionContext.ToUIThread" is done the execution switches to the UI thread.</para>
    /// <para>Whereas when a "yield return SwitchExecutionContext.ToWorkerThread" is done the execution switches to the UI thread.</para>
    /// <para>Such a method is started using the StartExecution method(s)</para>    
    /// <para>For Debugging purposes is it possible to make the AsyncExecutor execute everything on the UI thread by setting <see cref="SyncMode"/> to true</para>
    /// </summary>
    public class AsyncExecutor : PropertyChangedBase
    {
        /// <summary>
        /// <para>Initializes a new instance of the <see cref="AsyncExecutor"/> class.</para>
        /// <para>Has to be called on the UI thread!</para>
        /// </summary>
        public AsyncExecutor(Action<Exception> defaultErrorHandler)
        {
            DefaultErrorHandler = defaultErrorHandler;

            uiSyncContext = SynchronizationContext.Current;

            if (uiSyncContext == null)
                throw new ApplicationException("SynchronizationContext is null. Please make sure to call the ctor from the UI thread!");
        }

        Action<Exception> DefaultErrorHandler = null;

        private SynchronizationContext uiSyncContext = null;

        /// <summary>
        /// Gets or sets a value indicating whether the Async Executor should run synchronously. This is helpful for debugging.
        /// </summary>
        /// <value>Current Sync Mode</value>
        public bool SyncMode { get; set; }

        


        /// <summary>
        /// Starts the asynchronous execution using the given IEnumerator
        /// </summary>
        /// <param name="enumerator">The enumerator.</param>
        public void StartExecution(IEnumerator<SwitchExecutionContext> enumerator)
        {
            if (SyncMode)
            {
                Execute(enumerator);
            }
            else
            {
                Action<IEnumerator<SwitchExecutionContext>> call = enrt =>
                {
                    Thread currentThread = Thread.CurrentThread;                    
                    //We do this on the UI thread only - to avoid synchronization issues
                    uiSyncContext.Send(delegate { RunningThreads.Add(currentThread); IsBusy = true; }, null);                    
                    Execute(enrt);                                        
                    //We do this on the UI thread only - to avoid synchronization issues
                    uiSyncContext.Send(delegate {RunningThreads.Remove(currentThread); IsBusy = RunningThreads.Count > 0; }, null);
                };
                AsyncCallback callback = call.EndInvoke;
                call.BeginInvoke(enumerator, callback, null);
            }
        }

        //We keep track of the number of worker threads that are currently running to be able to set the IsBusy flag accordingly
        private HashSet<Thread> RunningThreads = new HashSet<Thread>();

        /// <summary>
        /// Executes the specified IEnumerator using the current thread as worker thread.
        /// </summary>
        /// <param name="enumerator">The enumerator.</param>
        public void Execute(IEnumerator<SwitchExecutionContext> enumerator)
        {
            if (SyncMode || (SynchronizationContext.Current != null && SynchronizationContext.Current.Equals(uiSyncContext)))
            {
                bool moveNext = true;
                while (moveNext)
                {
                    try
                    {
                        moveNext = enumerator.MoveNext();
                    }
                    catch (Exception ex)
                    {
                        DefaultErrorHandler(ex);
                        moveNext = false;
                    }
                }
            }
            else
            {
                bool moveNext = true;
                SwitchExecutionContext mode = SwitchExecutionContext.ToUIThread;
                while (moveNext)
                {
                    if (mode == SwitchExecutionContext.ToUIThread)
                    {
                        //On UI thread
                        uiSyncContext.Send(delegate
                        {
                            try
                            {
                                moveNext = enumerator.MoveNext();

                                if (moveNext)
                                {
                                    mode = enumerator.Current;
                                }
                            }
                            catch (Exception ex)
                            {
                                //Call the default error handler - we are already on the UI thread
                                DefaultErrorHandler(ex);
                                moveNext = false;                                
                            }
                        }, null);
                    }
                    else
                    {
                        //On the current (async) thread
                        try
                        {
                            moveNext = enumerator.MoveNext();

                            if (moveNext)
                            {
                                mode = enumerator.Current;
                            }
                        }
                        catch (Exception ex)
                        {
                            //Call the default error handler - on the UI thread
                            uiSyncContext.Send(delegate
                            {
                                DefaultErrorHandler(ex);
                            }, null);                            
                            moveNext = false;
                        }
                    }
                }
            }
        }

        

        #region Property: bool IsBusy
        /// <summary>
        /// Holder for IsBusy
        /// </summary>
        private bool isBusy;

        /// <summary>
        /// <para>IsBusy property</para>
        /// <para>Indicates whether the executor is busy with an execution.</para>
        /// <para>Can be cound to in the UI - it fires the INotifyPropertyChanged.PropertyChanged event</para>
        /// </summary>
        public bool IsBusy
        {
            get { return isBusy; }
            private set
            {
                isBusy = value;
                FirePropertyChanged("IsBusy");
            }
        }
        #endregion

       
    }    

    /// <summary>
    /// Enumeration for the two supported execution contexts
    /// </summary>
    public enum SwitchExecutionContext
    {
        /// <summary>
        /// Execution Context is UI Thread 
        /// </summary>
        ToUIThread, 
        /// <summary>
        /// Execution Context is Worker Thread 
        /// </summary>
        ToWorkerThread
    }   


}
